package edu.kit.aifb.ldsbench.generator2;

import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import org.openrdf.model.URI;
import org.openrdf.repository.RepositoryException;

import edu.kit.aifb.ldsbench.cli2.CLI;
import edu.kit.aifb.ldsbench.generator.opt.util2.DoubleInstanceBuffer;
import edu.kit.aifb.ldsbench.generator.opt2.IOptimizationSolver;
import edu.kit.aifb.ldsbench.generator.opt2.PropertyOptimizer;
import edu.kit.aifb.ldsbench.generator.util2.IDistributionValues;
import edu.kit.aifb.ldsbench.generator.util2.Octave;
import edu.kit.aifb.ldsbench.out2.IGeneratorOutput;

public class Generator implements Runnable{

	// size for the instance set and jitter value cache. IF numEvents is smaller then the value of numEvents is chosen.
	// note this buffer size is created twice.
	private static final int INSTANCE_BUFFER_SIZE = 1;
	private static final int JITTER_BUFFER_SIZE = 30;
	private static final int EVENT_SIZE_BUFFER_SIZE = 30;
	
	private int numEvents;
	
	private IDistributionValues distribValues;
	private IOptimizationSolver optSolver;
	private PropertyOptimizer propOptimizer;
	private IGeneratorOutput out;
	private DoubleInstanceBuffer instanceBuffer;
	
	public Generator(int numEvents, IGeneratorOutput out){
		this.numEvents = numEvents;
		this.out = out;
		Octave octave = new Octave();
		this.distribValues = octave;
		this.optSolver = octave;
		this.instanceBuffer = new DoubleInstanceBuffer(INSTANCE_BUFFER_SIZE);
		try {
			this.propOptimizer = new PropertyOptimizer(CLI.REPO.getConnection(), octave, octave, out, CLI.STATS);
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
		
	}

	@Override
	public void run() {
		//starting and triggering the buffers
		this.instanceBuffer.start();
		//the used jitterValues
		int jitterCounter = 0;
		long[] jitter = getJitterValues(this.numEvents);
		//the used instances
		int instCounter = 0;
		List<Map<URI, List<URI>>> instances = this.instanceBuffer.getBuffer();
		int eventCounter = 0;
		double[] eventLength = getEventLength(this.numEvents);
		
		
		for(int i = 0; i < this.numEvents; i++){
		
			if(eventCounter == eventLength.length){
				if(CLI.log.isLoggable(Level.FINE)) CLI.log.fine("Run out of eventLenghts.");
				eventLength = getEventLength(this.numEvents - i);
				eventCounter = 0;
			}
			if(instCounter == INSTANCE_BUFFER_SIZE){
				if(CLI.log.isLoggable(Level.FINE)) CLI.log.fine("Run out of instances.");
				instances = this.instanceBuffer.getBuffer();
				instCounter = 0;
			}
			//generate the event
			generate(instances.get(instCounter++), eventLength[eventCounter++]);
			if(CLI.log.isLoggable(Level.INFO)) CLI.log.info("Event nr: " + i + " generated.");
			//check all the buffers if we need to refill them

			if(jitterCounter == jitter.length){
				if(CLI.log.isLoggable(Level.FINE)) CLI.log.fine("Run out of jitterValues.");
				jitter = getJitterValues(this.numEvents - i);
				jitterCounter = 0;
			}
			try {
				//sleeping the jitter time
				Thread.sleep(jitter[jitterCounter++]);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		//shutting down properly
		shutdown();
	}
	
	
	private void generate(Map<URI, List<URI>> generationInstances, double eventSize){
		//optimizing the number of properties and sending it to the output
		this.propOptimizer.optimizePropertyOutput(eventSize, generationInstances);
	}
	
	private double[] getEventLength(int eventsLeft){
		int numValues;
		if(eventsLeft < EVENT_SIZE_BUFFER_SIZE){
			numValues = eventsLeft;
		}else{
			numValues = EVENT_SIZE_BUFFER_SIZE;
		}
		// getting the event lengths
		return this.distribValues.getDistributionValue(CLI.STATS.getBandwidthDistribution(), numValues);
	}
	
	private long[] getJitterValues(int eventsLeft){
		int numValues;
		if(eventsLeft < JITTER_BUFFER_SIZE){
			numValues = eventsLeft;
		}else{
			numValues = JITTER_BUFFER_SIZE;
		}
		// getting the jitter values
		double[] doubleJitter = this.distribValues.getDistributionValue(CLI.STATS.getJitterDistribution(), numValues);
		long[] jitter = new long[numValues];
		//we needs to convert them to long
		for(int i = 0; i < jitter.length; i++){
			jitter[i] = Math.abs((long) doubleJitter[i]);
		}
		return jitter;
	}
	
	public void shutdown(){
		this.out.close();
		this.distribValues.shutdown();
		if(!(this.distribValues == this.optSolver)){
			this.optSolver.shutdown();
		}
		this.instanceBuffer.shutdown();
		this.propOptimizer.shutdown();
	}
}
